Go 逐行读取文件 Scanner
当文件过大,不适合一次性载入内存,且文件每行都包含特定信息时,我们就需要逐行读取文件来保证程序的性能;
Go 语言有三种逐行读取文件的方法,依次是:
- ReadString
- ReadLine
- Scanner
下面分别介绍它们的用法
ReadString
ReadString 是一个位于 bufio 包的方法
func (b *Reader) ReadString(delim byte) (string, error)
ReadString 从输入中读取数据,直到分隔符出现,返回包括分割符在内的字符串。如果 ReadString 在找到一个分割符之前就遇到了错误,它会返回已读取的数据和错误本身。注意,这里需要裁切掉末尾的换行符
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
fileObj, _ := os.Open("file.txt")
defer fileObj.Close()
reader := bufio.NewReader(fileObj)
for {
if str, ok := readline(reader); ok {
fmt.Println(str)
continue
}
break
}
}
func readline(fi *bufio.Reader) (string, bool) {
s, err := fi.ReadString('\n')
if err != nil {
return "", false
}
return strings.Trim(s, "\r\n"), true
}
更多示例参考 Golang Reader.ReadString Examples
2、ReadLine 不介绍,这个是 bufio 实现 ReadString 的方法,但是注释上已经写了不建议使用
Scanner
下面就是这次的主角 Scanner 工具,它也是 bufio 包下的一个工具,从字面意思来看是一个扫描器、扫描仪。
所用是不停的从一个 reader 中读取数据兵缓存在内存中,还提供了一个注入函数用来自定义分割符。库中还提供了4个预定义分割方法。
- ScanLines:以换行符分割('n')
- ScanWords:返回通过“空格”分词的单词
- ScanRunes:返回单个 UTF-8 编码的 rune 作为一个 token
- ScanBytes:返回单个字节作为一个 token
使用例:
package main
import (
"fmt"
"os"
"bufio"
)
func main() {
fp,err := os.Open("file.txt")
if err!=nil{
fmt.Println(err) //打开文件错误
return
}
buf := bufio.NewScanner(fp)
for {
if !buf.Scan() {
break //文件读完了,退出for
}
line := buf.Text() //获取每一行
fmt.Println(line)
}
}
替换分割方式
上面说到 Scanner 提供了四个默认的分割方法,如何使用呢?只需简单的传入就行了(它是一个回调函数)
func main() {
fp, err := os.Open("./LICENSE")
if err != nil {
fmt.Println(err) //打开文件错误
return
}
scanner := bufio.NewScanner(fp)
scanner.Split(bufio.ScanWords) // 替换分割方式
for {
if !scanner.Scan() {
break //文件读完了,退出for
}
line := scanner.Text() //获取每一行
fmt.Println(line)
}
}
输出:
Public
License.
Notwithstanding
any
other
provision
of
this
License,
...
从指定行号开始读取
bufio.Scanner 默认不保存行号,所以这里需要简单拓展一下,这里使用 Seeker 接口来用做读取的偏移量
这里不使用默认的切割函数自定义一个 bufio.SplitFunc
,这个函数签名为:
type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)
它返回的 advance 表示前进了多少位,token 表示本次切割从起点到终点所包含的内容,所以这里直接利用原本的 ScanLines 进行拓展。
func withScanner(input io.ReadSeeker, start int64) error {
fmt.Println("--SCANNER, start:", start)
if _, err := input.Seek(start, 0); err != nil {
return err
}
scanner := bufio.NewScanner(input)
pos := start
scanLines := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
advance, token, err = bufio.ScanLines(data, atEOF)
pos += int64(advance)
return
}
scanner.Split(scanLines)
for scanner.Scan() {
fmt.Printf("Pos: %d, Scanned: %s\n", pos, scanner.Text())
}
return scanner.Err()
}
References
How to read a file starting from a specific line number using Scanner? How to use bufio.ScanWords Scanner Documentation Golang 逐行读写之scanner.Scan